// (c) Denis Yurin Class to read fof files from Gadget3 v2.0
#ifndef FOFR_H
#define FOFR_H

#include <iostream>
#include <stdio.h>
#include <string.h>

#include "utils/utils.02.h"

struct FoFGroup {

	uint Len;
	uint Offset;
	float Mass;
	float CM[3];
	float Vel[3];
	uint LenType[6];
	float MassType[6];
	uint *ids;
	
};

class FoFR {
	public:	
		typedef unsigned int uint;
		typedef long unsigned int luint;
		
		uint Ngroups;
		uint TotNgroups;
		uint Nids;
		luint TotNids;
		uint NTask;
		uint Offset;
		
		//arrays
		FoFGroup *grp; 
		uint 		*ids;
		
		uint SnapshotNr;
		
		Timer tr;
		
		FoFR():grp(NULL), ids(NULL) { };
	  ~FoFR() { 
			if (grp!=NULL) delete [] grp;
			if (ids!=NULL) delete [] ids; 
		};

		// reads data from tab file
		uint read_tab(const char *filename){
			
			
			//
			uint filesize = getFileSize(filename);
			char *mem = new char[filesize];
			char *mem0 = mem;			
			
			// reads hole file at once
			FILE *fs = fopen(filename, "r");
			if (fs==NULL) return 0;
				
			fread(mem, 1, filesize, fs); 	// in local file
			fclose(fs);
			
			Ngroups 	= *(uint*)mem; mem+=sizeof(uint);
			TotNgroups = *(uint*)mem; mem+=sizeof(uint);
			Nids 		= *(uint*)mem; mem+=sizeof(uint);
			TotNids 	= *(luint*)mem; mem+=sizeof(luint);
			NTask 	= *(uint*)mem; mem+=sizeof(uint);
			
			if (grp) delete [] grp;
			grp = new FoFGroup[Ngroups];
			
			for (uint i=0; i<Ngroups; i++) 	{	grp[i].Len    = *(uint*)mem;	mem+=sizeof(uint);	}
			for (uint i=0; i<Ngroups; i++) 	{	grp[i].Offset = *(uint*)mem; 	mem+=sizeof(uint);	}
			for (uint i=0; i<Ngroups; i++) 	{	grp[i].Mass   = *(float*)mem; mem+=sizeof(float);	}
			for (uint i=0; i<Ngroups; i++) 	{	memcpy(grp[i].CM,  	   mem, 3*sizeof(float)); 	mem+=3*sizeof(float); }
			for (uint i=0; i<Ngroups; i++) 	{	memcpy(grp[i].Vel,	   mem, 3*sizeof(float)); 	mem+=3*sizeof(float); }
			for (uint i=0; i<Ngroups; i++) 	{	memcpy(grp[i].LenType,  mem, 6*sizeof(uint)); 	mem+=6*sizeof(uint);  }
			for (uint i=0; i<Ngroups; i++) 	{	memcpy(grp[i].MassType, mem, 6*sizeof(float)); 	mem+=6*sizeof(float); }
				
			delete [] mem0;

		}


		// reads data from tab and ids files
		uint read(const char *foldername, uint SnapshotNr, uint TaskID){

			char filename_tab[400]; sprintf(filename_tab, "%s/groups_%03u/group_tab_%03u.%u", foldername, SnapshotNr, SnapshotNr, TaskID);
			char filename_ids[400];	sprintf(filename_ids, "%s/groups_%03u/group_ids_%03u.%u", foldername, SnapshotNr, SnapshotNr, TaskID);
			
			read_tab(filename_tab);
			
			uint filesize = getFileSize(filename_ids);
			char *mem = new char[filesize];
			char *mem0(mem);
			
			// reads hole file at once
			FILE *fs = fopen(filename_ids, "r");
			if (fs==NULL) return 0;
				
			fread(mem, 1, filesize, fs); 	// in local file
			fclose(fs);
			
			// skip most header information
			mem+=sizeof(Ngroups)+sizeof(TotNgroups)+sizeof(Nids)+sizeof(TotNids)+sizeof(NTask);
			
			// reads only missing Offset
			Offset = *(uint*)mem; mem+=sizeof(uint);
			
			// 
			if (ids) delete [] ids;
			ids = new uint[Nids];
			memcpy(ids, mem, Nids*sizeof(uint));
			
			uint *_ids(ids);
			for (uint i=0; i<Ngroups; i++) {
				grp[i].ids = _ids; _ids+=grp[i].Len;
			}
			
			delete [] mem0;
			return 1;
			
		}

		//reads data from whole group
		uint read(const char *foldername, uint SnapshotNr){
			
			tr.set();
			
			this->SnapshotNr=SnapshotNr;
			
			char filename_tab[400];
			sprintf(filename_tab, "%s/groups_%03u/group_tab_%03u.%u", foldername, SnapshotNr, SnapshotNr, 0);
			read_tab(filename_tab);
			delete [] grp; grp=NULL;

			FoFR *fofr = new FoFR[NTask];
			for (uint i=0; i<NTask; i++) 
				if (!fofr[i].read(foldername, SnapshotNr, i)) return 0;
			
			// allocates new mem for all particles and frees it if it was occupied 
			if (grp) delete [] grp;
			grp = new FoFGroup[TotNgroups];
			
			if (ids) delete [] ids;
			ids = new uint[TotNids];
			
			// to keep begings of arrays
			FoFGroup *_grp(grp);
			uint *_ids(ids);
			
			// copies data from all files together 
			for (uint i=0; i<NTask; i++)  {
				memcpy(_grp, fofr[i].grp, fofr[i].Ngroups*sizeof(FoFGroup) );
				memcpy(_ids, fofr[i].ids, fofr[i].Nids*sizeof(uint) );
				_grp += fofr[i].Ngroups;
				_ids += fofr[i].Nids;
			}
			
			// now every grop will know its ids
			_ids=ids;
			for (uint i=0; i<TotNgroups; i++) {
				grp[i].ids = _ids;
				_ids+=grp[i].Len;
			}
			
			tr.get();
			
			return 1; // all is ok!
		}
		
		void print_tab(){
			
			
			printf("\n"); 
			printf("\033[22;34mTotNgroups\033[22;30m: %u\n", TotNgroups); 
			printf("\033[22;34mTotNids\033[22;30m: %u\n", TotNids);
			printf("\033[22;34mNTask\033[22;30m: %u\n", NTask); 
			
		}
		
		void print_group(uint id){
			
			printf("\n");
			printf("\033[01;38mGroup[\033[01;34m%u\033[01;38m]\033[22;30m\n", id); 
			printf("├\033[22;34mLen\033[22;30m: %u\n", grp[id].Len); 
			printf("├\033[22;34mOffset\033[22;30m: %u\n", grp[id].Offset); 
			printf("├\033[22;34mMass\033[22;30m: %g\n", grp[id].Mass); 
			printf("├\033[22;34mCM\033[22;30m: %g, %g, %g\n", grp[id].CM[0], grp[id].CM[1], grp[id].CM[2]);
			printf("├\033[22;34mVel\033[22;30m: %g, %g, %g\n", grp[id].Vel[0], grp[id].Vel[1], grp[id].Vel[2]);
			printf("└\033[22;34mIds\033[22;30m: ");
			print_ids(id, 50); // to print all jast ser big secon number
			printf("\n");
			
		}
		
		void print_ids(uint id, uint nprint){
			
			if (nprint < grp[id].Len) {
				for (uint j=0; j<=nprint; j++) {
					printf("%u", grp[id].ids[uint((grp[id].Len-1)*double(j)/nprint)]);
					if (j<nprint) printf(", ,");
				}
			} else 
				for (uint i=0; i<grp[id].Len; i++)  
					printf("%u, ", grp[id].ids[i]);
				
			printf("\n");
		}
		
		

		
		
		void print(){
			
			printf("\033[22;34mSnapshotNr\033[22;30m: %u\n", SnapshotNr); 
			printf("\033[22;34mFileProcess Time\033[22;30m: %g \033[22;37msec\033[22;30m\n", tr.sin); 
			print_tab();
			print_group(0);
			//print_group(1);
			//print_group(2);
			
		}
			
			

};

#endif
